package pathfinding.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;

import pathfinding.CellTerrainType;
import pathfinding.SettingsObject;
import pathfinding.gui.MapCell.CellIkonType;
import pathfinding.gui.listeners.ComboBoxListener;
import pathfinding.gui.listeners.ToggleButtonListener;
import pathfinding.gui.listeners.UserActionListener;
import pathfinding.gui.listeners.ZoomWheelListener;
import algorithms.PathFindingAlgorithm;
import algorithms.PathFindingAlgorithms;
import algorithms.astar.AStarAlgorithm;
import algorithms.astar.heuristics.HCostHeuristic;

/**
 * SettingsPanel class extending JPanel.
 * 
 * Contains all GUI components used for making settings for
 * application.
 *
 * @author teturun
 */
public class SettingsPanel extends JPanel {

	private int PANEL_WIDTH = 150;
	
	private PathFindingApplet master;
	
	private JComboBox algorithmComboBox;
	private JComboBox aStarHeuristicsComboBox;
	private JPanel extraSettingsPanelForAStar;
	
	private JButton findButton;
	private JButton clearButton;
	private JButton addNewMapButton;
	private JButton copyMapButton;
	private JButton deleteMapButton;
	
	private JToggleButton floorButton;
	private JToggleButton wallButton;
	private JToggleButton startButton;
	private JToggleButton goalButton;
	
	private JCheckBox clearPathEndPoints;
	
	private AddMapDialog addMapDialog;
	
	/**
	 * Constructor for SettingsPanel.
	 * 
	 * @param master
	 */
	public SettingsPanel(PathFindingApplet master) {
		this.master = master;
		PathFindingAlgorithms algorithms = new PathFindingAlgorithms();
		this.buildPanel(algorithms);
	}
	
	/**
	 * Builds the settings panel.
	 * 
	 * @param algorithms path finding algorithms used in this application
	 */
	private void buildPanel(PathFindingAlgorithms algorithms) {
		
		this.setPreferredSize(new Dimension(PANEL_WIDTH, 200));
		this.setMinimumSize(new Dimension(PANEL_WIDTH, 200));
		this.setMaximumSize(new Dimension(PANEL_WIDTH, 200));
		this.setLayout(new FlowLayout());
		this.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, Color.BLACK));

		this.algorithmComboBox = new JComboBox(algorithms.getAlgorithms());
		this.algorithmComboBox.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.algorithmComboBox.addItemListener(new ComboBoxListener(this));

		UserActionListener listener = new UserActionListener(this);

		this.findButton = new JButton("Make search");
		this.findButton.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.findButton.addActionListener(listener);	
		
		this.clearButton = new JButton("Clear search results");
		this.clearButton.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.clearButton.addActionListener(listener);
		
		this.clearPathEndPoints = new JCheckBox("Include path endpoints");
		
		this.add(new JLabel("Select algorithm:"));
		this.add(this.algorithmComboBox);
		
		this.extraSettingsPanelForAStar = this.extraSettingsForAStarAlgorithm(algorithms.getAlgorithms());
		
		if (this.extraSettingsPanelForAStar != null) {
			this.add(this.extraSettingsPanelForAStar);
			this.extraSettingsPanelForAStar.setVisible(false);
		}
	
		this.add(new JLabel("Map settings:"));
		this.add(createMapModifyingButtons(listener));
		this.add(findButton);
		this.add(clearButton);
		this.add(this.clearPathEndPoints);
		
		this.algorithmComboBox.setSelectedIndex(0);
		this.comboBoxItemChangePerformed(algorithmComboBox);
		
		//this.addMapDialog = new AddMapDialog(this.master, this);
	}
	
	/**
	 * Creates and returns own JPanel for A* path finding algorithm if needed.
	 * JPanel contains GUI components for selecting H cost heuristic for A*.
	 * 
	 * @param algorithms path finding algorithms used in this application
	 * @return built JPanel if A* is in used algorithms, null if doesn't
	 */
	private JPanel extraSettingsForAStarAlgorithm(Vector<PathFindingAlgorithm> algorithms) {
		
		for (PathFindingAlgorithm algorithm : algorithms) {
			
			if (algorithm instanceof AStarAlgorithm) {
				
				JPanel panel = new JPanel();
				panel.setPreferredSize(new Dimension(PANEL_WIDTH, 70));
				
				this.aStarHeuristicsComboBox = new JComboBox(((AStarAlgorithm) algorithm).getCostHeuristics().getHeuristics());
				this.aStarHeuristicsComboBox.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
				this.aStarHeuristicsComboBox.addItemListener(new ComboBoxListener(this));
				this.aStarHeuristicsComboBox.setSelectedIndex(0);
				this.comboBoxItemChangePerformed(this.aStarHeuristicsComboBox);
				
				panel.add(new JLabel("Choose heuristic for A*:"));
				panel.add(this.aStarHeuristicsComboBox);
				
				return panel;
				
			}
			
		}
		
		return null;
	}
	
	/**
	 * Creates JPanel containing buttons for modifying map that is shown.
	 * 
	 * @param listener for JButtons
	 * @return built JPanel including control buttons
	 */
	private JPanel createMapModifyingButtons(UserActionListener listener) {
		
		JPanel buttonPanel = new JPanel();
		buttonPanel.setPreferredSize(new Dimension(PANEL_WIDTH, 200));
		buttonPanel.setMinimumSize(new Dimension(PANEL_WIDTH, 200));
		buttonPanel.setMaximumSize(new Dimension(PANEL_WIDTH, 200));
		
		this.addNewMapButton = new JButton("Add new map");
		this.addNewMapButton.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.addNewMapButton.addActionListener(listener);
		
		this.copyMapButton = new JButton("Copy current map");
		this.copyMapButton.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.copyMapButton.addActionListener(listener);
		
		this.deleteMapButton = new JButton("Delete current map");
		this.deleteMapButton.setPreferredSize(new Dimension(PANEL_WIDTH, 30));
		this.deleteMapButton.addActionListener(listener);
		
		ToggleButtonListener toggleButtonListener = new ToggleButtonListener(this);
		
		ButtonGroup bGroup = new ButtonGroup();
		
		this.floorButton = new JToggleButton("Floor");
		this.floorButton.setPreferredSize(new Dimension(70,35));
		this.floorButton.addItemListener(toggleButtonListener);
		
		this.wallButton = new JToggleButton("Wall");
		this.wallButton.setPreferredSize(new Dimension(70,35));
		this.wallButton.addItemListener(toggleButtonListener);
		
		this.startButton = new JToggleButton("Start");
		this.startButton.setPreferredSize(new Dimension(70,35));
		this.startButton.addItemListener(toggleButtonListener);
		
		this.goalButton = new JToggleButton("Goal");
		this.goalButton.setPreferredSize(new Dimension(70,35));
		this.goalButton.addItemListener(toggleButtonListener);
		
		bGroup.add(this.floorButton);
		bGroup.add(this.wallButton);
		bGroup.add(this.startButton);
		bGroup.add(this.goalButton);
		
		buttonPanel.add(this.addNewMapButton);
		buttonPanel.add(this.copyMapButton);
		buttonPanel.add(this.deleteMapButton);
		buttonPanel.add(this.floorButton);
		buttonPanel.add(this.wallButton);
		buttonPanel.add(this.startButton);
		buttonPanel.add(this.goalButton);
		
		this.copyMapButton.setEnabled(false);
		this.deleteMapButton.setEnabled(false);
		
		return buttonPanel;
	}
	
	/**
	 * Method to be called when JDialog's (for adding maps)
	 * OK button is clicked.
	 * 
	 * Creates new map according to user settings and adds it.
	 * 
	 */
	public void dialogOkPerformed() {
		this.addMapDialog.setVisible(false);
		Dimension size = this.addMapDialog.getMapSize();
		Map map = new Map(size.width, size.height);
		map.addMouseWheelListener(new ZoomWheelListener(this.master));

		if (this.addMapDialog.createRandomMap()) {
			map.randomMap();
		}
		else {
			map.clearMap();
		}
		
		JTabbedPane tabPane = this.master.getTabbedPane();
		JScrollPane scrollPane = new JScrollPane(map);
		tabPane.add(scrollPane, "Map");
		tabPane.setSelectedComponent(scrollPane);
		
		this.copyMapButton.setEnabled(true);
		this.deleteMapButton.setEnabled(true);
	}
	
	/**
	 * Method to be called when JDialog's (for adding maps)
	 * Cancel button is clicked.
	 * 
	 * Hides the dialog without doing any changes.
	 * 
	 */
	public void dialogCancelPerformed() {
		this.addMapDialog.setVisible(false);
	}
	
	/**
	 * Method to be called when JComboBox's (used for choosing algorithm to be used 
	 * and heuristics for A*) item is changed.
	 * 
	 */
	public void comboBoxItemChangePerformed(JComboBox comboBox) {
		
		if (comboBox.equals(this.algorithmComboBox)) {
			
			if (! (comboBox.getSelectedItem() instanceof PathFindingAlgorithm)) {
				return;
			}
			
			PathFindingAlgorithm algorithm = (PathFindingAlgorithm)comboBox.getSelectedItem();
			SettingsObject.getInstance().setAlgorithm(algorithm);
			
			if (algorithm instanceof AStarAlgorithm) {
				if (this.extraSettingsPanelForAStar != null)
					this.extraSettingsPanelForAStar.setVisible(true);
			}
			else {
				if (this.extraSettingsPanelForAStar != null)
					this.extraSettingsPanelForAStar.setVisible(false);
			}
			
		}
		else if (comboBox.equals(this.aStarHeuristicsComboBox)) {
			
			if (! (this.algorithmComboBox.getSelectedItem() instanceof PathFindingAlgorithm)) {
				return;
			}
			
			PathFindingAlgorithm algorithm = (PathFindingAlgorithm)this.algorithmComboBox.getSelectedItem();
			
			if (algorithm instanceof AStarAlgorithm && 
				comboBox.getSelectedItem() instanceof HCostHeuristic) {
				
				AStarAlgorithm aStarAlgorithm = (AStarAlgorithm) algorithm;
				HCostHeuristic heuristic = (HCostHeuristic) comboBox.getSelectedItem();
				aStarAlgorithm.setHCostHeuristic(heuristic);
				
				System.out.println("HCostHeuristic: " + heuristic);
			}
			
		}
		
	}
	
	/**
	 * Method to be called when JButtons are clicked (e.g. for
	 * making search or clearing search results).
	 * 
	 * @param source
	 */
	public void buttonClickPerformed(JButton source) {
		
		if (source.equals(this.findButton)) {
			System.out.println("make search");
			JTabbedPane tabPane = this.master.getTabbedPane();
			
			if (! (tabPane.getSelectedComponent() instanceof JScrollPane)) {
				return;
			}
			JScrollPane jscroll = (JScrollPane)tabPane.getSelectedComponent();
			
			if (! (jscroll.getViewport().getView() instanceof Map)) {
				return;
			}
			
			Map map = (Map) jscroll.getViewport().getView();
			
			if (map.getStartCell() != null && map.getGoalCell() != null) {
				map.clearSearchResultsWithoutPathEndpoints();
				Point start = map.getStartCell().getPoint();
				Point goal = map.getGoalCell().getPoint();
				map.setSearchResultsToMap(SettingsObject.getInstance().
						getAlgorithm().findPath(start, goal, map));
			}
			else {
				JOptionPane.showMessageDialog(this.master,
					    "Please, set both start and goal point first!");

			}
			
		}
		else if (source.equals(this.clearButton)) {
			System.out.println("clear");
			
			JTabbedPane tabPane = this.master.getTabbedPane();
			if (! (tabPane.getSelectedComponent() instanceof JScrollPane)) {
				return;
			}
			JScrollPane jscroll = (JScrollPane)tabPane.getSelectedComponent();
			
			if (! (jscroll.getViewport().getView() instanceof Map)) {
				return;
			}
			
			Map map = (Map) jscroll.getViewport().getView();
			
			if (this.clearPathEndPoints.isSelected()) {
				map.clearSearchResultsAndPathEndpoints();
			}
			else  {
				map.clearSearchResultsWithoutPathEndpoints();
			}
			
		}
		else if (source.equals(this.addNewMapButton)) {
			
			if (this.addMapDialog == null)
				this.addMapDialog = new AddMapDialog(this.master, this);
			this.addMapDialog.setVisible(true);
		}
		else if (source.equals(this.copyMapButton)) {
			
			JTabbedPane tabPane = this.master.getTabbedPane();
			if (! (tabPane.getSelectedComponent() instanceof JScrollPane)) {
				return;
			}
			JScrollPane jscroll = (JScrollPane)tabPane.getSelectedComponent();
			
			if (! (jscroll.getViewport().getView() instanceof Map)) {
				return;
			}
			
			Map map = (Map) jscroll.getViewport().getView();

			Map copyMap = map.getCopyOfMapTerrain();
			copyMap.addMouseWheelListener(new ZoomWheelListener(this.master));
			
			JScrollPane scrollPane = new JScrollPane(copyMap);
			
			tabPane.add(scrollPane, "Map");
			tabPane.setSelectedComponent(scrollPane);
		}
		else if (source.equals(this.deleteMapButton)) {
			JTabbedPane tabPane = this.master.getTabbedPane();
			
			if (! (tabPane.getSelectedComponent() instanceof JScrollPane)) {
				return;
			}
			JScrollPane jscroll = (JScrollPane)tabPane.getSelectedComponent();
			
			System.out.println("Delete current map");
			int removeIndex = tabPane.getSelectedIndex();
			System.out.println("Delete current map " + removeIndex);
			if (removeIndex >= 1) { // first tab (index 0) is for start panel
				// For some reasons if you try to remove
				// selected tab directly with tabPane.remove()
				// it takes a very long time, but when you instead
				// first do like below, it is much faster
				if (! (jscroll.getViewport().getView() instanceof Map)) {
					return;
				}
				
				Map map = (Map) jscroll.getViewport().getView();
				//Map map = (Map)tabPane.getSelectedComponent();
				map.removeAll();
				tabPane.remove(jscroll);
				
				if (tabPane.getComponentCount() == 1) { // only welcome tab in tab pane
					this.copyMapButton.setEnabled(false);
					this.deleteMapButton.setEnabled(false);
				}
			}
			System.out.println("Delete current map done");
		}
		
	}
	
	/**
	 * Method to be called when JToggleButton for different
	 * map cell types is clicked.
	 * 
	 * @param source
	 */
	public void toggleButtonClickPerformed(JToggleButton source) {
		
		if (source.equals(this.startButton)) {
			SettingsObject.getInstance().setCellRole(CellIkonType.START);
		}
		else if (source.equals(this.goalButton)) {
			SettingsObject.getInstance().setCellRole(CellIkonType.GOAL);
		}
		else if (source.equals(this.wallButton)) {
			SettingsObject.getInstance().setCellTerrainType(CellTerrainType.WALL);
		}
		else if (source.equals(this.floorButton)) {
			SettingsObject.getInstance().setCellTerrainType(CellTerrainType.FLOOR);
		}
		
	}
	
}
